/*
 * Copyright 2009 XueSong Guo.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.webwheel.utils;

import cn.webwheel.Filter;
import cn.webwheel.FilterChain;
import cn.webwheel.WebContext;
import cn.webwheel.di.*;
import cn.webwheel.di.engine.DefaultContainer;
import cn.webwheel.di.utils.TypeLiteral;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

/**
 * WebWheel DI容器应用于WebWheel MVC框架中的过滤器。<br>
 * 并且绑定了{@link cn.webwheel.WebContext}、
 * {@link javax.servlet.http.HttpServletRequest}、
 * {@link javax.servlet.http.HttpServletResponse}、
 * {@link javax.servlet.http.HttpSession}的动态注入。<br>
 * 并且通过映射{@link cn.webwheel.utils.WebParam}注释接口实现了web参数的动态注入，支持以下类型的web参数注入：<br>
 * Map&lt;String, String[]&gt;、String、String[]、boolean、byte、byte[]、short、short[]、int、int[]、long、long[]、float、float[]、double、double[]<br>
 * 通过(@link WebCookie}注释接口实现cookie参数的注入，支持以下类型：<br>
 * Cookie[]、Cookie、String、boolean、byte、short、int、long、float、double
 */
public class DIFilter implements Filter {

    private final ThreadLocal<WebContext> threadLocal = new ThreadLocal<WebContext>();

    private final Provider<Cookie> cookieProvider = new Provider<Cookie>() {
        public Cookie get(Key<Cookie> key, String data, NativeProvider<?> nativeProvider) {
            WebContext ctx = threadLocal.get();
            if (ctx == null) return null;
            Cookie[] cookies = ctx.getRequest().getCookies();
            if (cookies == null) return null;
            for (Cookie cookie : cookies) {
                if(cookie.getName().equals(data)) return cookie;
            }
            return null;
        }
    };

    private Container container;
    private String charset;

    /**
     * 构造方法。
     * @param container DI容器
     * @param charset request编码
     */
    public DIFilter(Container container, String charset) {
        this.container = container;
        this.charset = charset;
    }

    @SuppressWarnings("unchecked")
    public void init() {
        container.bind(new Key<WebContext>(WebContext.class, Desire.DefaultName), new Provider<WebContext>() {
            public WebContext get(Key<WebContext> webContextKey, String data, NativeProvider<?> nativeProvider) {
                return threadLocal.get();
            }
        });

        container.bind(new Key<HttpServletRequest>(HttpServletRequest.class, Desire.DefaultName), new Provider<HttpServletRequest>() {
            public HttpServletRequest get(Key<HttpServletRequest> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                return ctx.getRequest();
            }
        });
        container.bind(new Key<HttpServletResponse>(HttpServletResponse.class, Desire.DefaultName), new Provider<HttpServletResponse>() {
            public HttpServletResponse get(Key<HttpServletResponse> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                return ctx.getResponse();
            }
        });
        container.bind(new Key<HttpSession>(HttpSession.class, Desire.DefaultName), new Provider<HttpSession>() {
            public HttpSession get(Key<HttpSession> httpSessionKey, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                return ctx.getRequest().getSession();
            }
        });
        if (container instanceof DefaultContainer) {
            ((DefaultContainer)container).addNamedDesire(WebParam.class.getSimpleName(), WebParam.class);
            ((DefaultContainer)container).addNamedDesire(WebCookie.class.getSimpleName(), WebCookie.class);
        }
        container.bind(new Key<Map<String, String[]>>(new TypeLiteral<Map<String, String[]>>(){}.getType(), WebParam.class.getSimpleName()), new Provider<Map<String, String[]>>() {
            public Map<String, String[]> get(Key<Map<String, String[]>> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                return ctx.getRequest().getParameterMap();
            }
        });
        container.bind(new Key<Map<String, String>>(new TypeLiteral<Map<String, String>>(){}.getType(), WebParam.class.getSimpleName()), new Provider<Map<String, String>>() {
            public Map<String, String> get(Key<Map<String, String>> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                Map<String, String[]> map = ctx.getRequest().getParameterMap();
                Map<String, String> nmap = new HashMap<String, String>(map.size());
                for (Map.Entry<String, String[]> entry : map.entrySet()) {
                    nmap.put(entry.getKey(), entry.getValue()[0]);
                }
                return nmap;
            }
        });
        container.bind(new Key<String>(String.class, WebParam.class.getSimpleName()), new Provider<String>() {
            public String get(Key<String> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                return ctx.getRequest().getParameter(data);
            }
        });
        container.bind(new Key<String[]>(String[].class, WebParam.class.getSimpleName()), new Provider<String[]>() {
            public String[] get(Key<String[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                return ctx.getRequest().getParameterValues(data);
            }
        });
        container.bind(new Key<Boolean>(boolean.class, WebParam.class.getSimpleName()), new Provider<Boolean>() {
            public Boolean get(Key<Boolean> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                return ctx != null && ctx.getRequest().getParameter(data) != null;
            }
        });
        container.bind(new Key<Byte>(byte.class, WebParam.class.getSimpleName()), new Provider<Byte>() {
            public Byte get(Key<Byte> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return 0;
                String s = ctx.getRequest().getParameter(data);
                if(s==null) return 0;
                try {
                    return Byte.parseByte(s.trim());
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
        });
        container.bind(new Key<byte[]>(byte[].class, WebParam.class.getSimpleName()), new Provider<byte[]>() {
            public byte[] get(Key<byte[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                String[] ss = ctx.getRequest().getParameterValues(data);
                if (ss == null) return null;
                byte[] is = new byte[ss.length];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        is[i] = Byte.parseByte(ss[i].trim());
                    } catch (NumberFormatException e) {
                        //
                    }
                }
                return is;
            }
        });
        container.bind(new Key<Short>(short.class, WebParam.class.getSimpleName()), new Provider<Short>() {
            public Short get(Key<Short> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return 0;
                String s = ctx.getRequest().getParameter(data);
                if(s==null) return 0;
                try {
                    return Short.parseShort(s.trim());
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
        });
        container.bind(new Key<short[]>(short[].class, WebParam.class.getSimpleName()), new Provider<short[]>() {
            public short[] get(Key<short[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                String[] ss = ctx.getRequest().getParameterValues(data);
                if (ss == null) return null;
                short[] is = new short[ss.length];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        is[i] = Short.parseShort(ss[i].trim());
                    } catch (NumberFormatException e) {
                        //
                    }
                }
                return is;
            }
        });
        container.bind(new Key<Integer>(int.class, WebParam.class.getSimpleName()), new Provider<Integer>() {
            public Integer get(Key<Integer> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return 0;
                String s = ctx.getRequest().getParameter(data);
                if(s==null) return 0;
                try {
                    return Integer.parseInt(s.trim());
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
        });
        container.bind(new Key<int[]>(int[].class, WebParam.class.getSimpleName()), new Provider<int[]>() {
            public int[] get(Key<int[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                String[] ss = ctx.getRequest().getParameterValues(data);
                if (ss == null) return null;
                int[] is = new int[ss.length];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        is[i] = Integer.parseInt(ss[i].trim());
                    } catch (NumberFormatException e) {
                        //
                    }
                }
                return is;
            }
        });
        container.bind(new Key<Long>(long.class, WebParam.class.getSimpleName()), new Provider<Long>() {
            public Long get(Key<Long> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return 0L;
                String s = ctx.getRequest().getParameter(data);
                if(s==null) return 0L;
                try {
                    return Long.parseLong(s.trim());
                } catch (NumberFormatException e) {
                    return 0L;
                }
            }
        });
        container.bind(new Key<long[]>(long[].class, WebParam.class.getSimpleName()), new Provider<long[]>() {
            public long[] get(Key<long[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                String[] ss = ctx.getRequest().getParameterValues(data);
                if (ss == null) return null;
                long[] is = new long[ss.length];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        is[i] = Long.parseLong(ss[i].trim());
                    } catch (NumberFormatException e) {
                        //
                    }
                }
                return is;
            }
        });
        container.bind(new Key<Float>(float.class, WebParam.class.getSimpleName()), new Provider<Float>() {
            public Float get(Key<Float> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return 0f;
                String s = ctx.getRequest().getParameter(data);
                if(s==null) return 0f;
                try {
                    return Float.parseFloat(s.trim());
                } catch (NumberFormatException e) {
                    return 0f;
                }
            }
        });
        container.bind(new Key<float[]>(float[].class, WebParam.class.getSimpleName()), new Provider<float[]>() {
            public float[] get(Key<float[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                String[] ss = ctx.getRequest().getParameterValues(data);
                if (ss == null) return null;
                float[] is = new float[ss.length];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        is[i] = Float.parseFloat(ss[i].trim());
                    } catch (NumberFormatException e) {
                        //
                    }
                }
                return is;
            }
        });
        container.bind(new Key<Double>(double.class, WebParam.class.getSimpleName()), new Provider<Double>() {
            public Double get(Key<Double> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return 0.0;
                String s = ctx.getRequest().getParameter(data);
                if(s==null) return 0.0;
                try {
                    return Double.parseDouble(s.trim());
                } catch (NumberFormatException e) {
                    return 0.0;
                }
            }
        });
        container.bind(new Key<double[]>(double[].class, WebParam.class.getSimpleName()), new Provider<double[]>() {
            public double[] get(Key<double[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                String[] ss = ctx.getRequest().getParameterValues(data);
                if (ss == null) return null;
                double[] is = new double[ss.length];
                for (int i = 0; i < ss.length; i++) {
                    try {
                        is[i] = Double.parseDouble(ss[i].trim());
                    } catch (NumberFormatException e) {
                        //
                    }
                }
                return is;
            }
        });

        container.bind(new Key<Cookie[]>(Cookie[].class, WebCookie.class.getSimpleName()), new Provider<Cookie[]>() {
            public Cookie[] get(Key<Cookie[]> key, String data, NativeProvider<?> nativeProvider) {
                WebContext ctx = threadLocal.get();
                if (ctx == null) return null;
                return ctx.getRequest().getCookies();
            }
        });
        container.bind(new Key<Cookie>(Cookie.class, WebCookie.class.getSimpleName()), cookieProvider);
        container.bind(new Key<String>(String.class, WebCookie.class.getSimpleName()), new Provider<String>() {
            public String get(Key<String> key, String data, NativeProvider<?> nativeProvider) {
                Cookie c = cookieProvider.get(null, data, null);
                return c == null ? null : c.getValue();
            }
        });
        container.bind(new Key<Boolean>(boolean.class, WebCookie.class.getSimpleName()), new Provider<Boolean>() {
            public Boolean get(Key<Boolean> key, String data, NativeProvider<?> nativeProvider) {
                return cookieProvider.get(null, data, null) != null;
            }
        });
        container.bind(new Key<Byte>(byte.class, WebCookie.class.getSimpleName()), new Provider<Byte>() {
            public Byte get(Key<Byte> key, String data, NativeProvider<?> nativeProvider) {
                Cookie c = cookieProvider.get(null, data, null);
                if(c==null) return 0;
                try {
                    return Byte.parseByte(c.getValue());
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
        });
        container.bind(new Key<Short>(short.class, WebCookie.class.getSimpleName()), new Provider<Short>() {
            public Short get(Key<Short> key, String data, NativeProvider<?> nativeProvider) {
                Cookie c = cookieProvider.get(null, data, null);
                if(c==null) return 0;
                try {
                    return Short.parseShort(c.getValue());
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
        });
        container.bind(new Key<Integer>(int.class, WebCookie.class.getSimpleName()), new Provider<Integer>() {
            public Integer get(Key<Integer> key, String data, NativeProvider<?> nativeProvider) {
                Cookie c = cookieProvider.get(null, data, null);
                if(c==null) return 0;
                try {
                    return Integer.parseInt(c.getValue());
                } catch (NumberFormatException e) {
                    return 0;
                }
            }
        });
        container.bind(new Key<Long>(long.class, WebCookie.class.getSimpleName()), new Provider<Long>() {
            public Long get(Key<Long> key, String data, NativeProvider<?> nativeProvider) {
                Cookie c = cookieProvider.get(null, data, null);
                if(c==null) return 0L;
                try {
                    return Long.parseLong(c.getValue());
                } catch (NumberFormatException e) {
                    return 0L;
                }
            }
        });
        container.bind(new Key<Float>(float.class, WebCookie.class.getSimpleName()), new Provider<Float>() {
            public Float get(Key<Float> key, String data, NativeProvider<?> nativeProvider) {
                Cookie c = cookieProvider.get(null, data, null);
                if(c==null) return 0f;
                try {
                    return Float.parseFloat(c.getValue());
                } catch (NumberFormatException e) {
                    return 0f;
                }
            }
        });
        container.bind(new Key<Double>(double.class, WebCookie.class.getSimpleName()), new Provider<Double>() {
            public Double get(Key<Double> key, String data, NativeProvider<?> nativeProvider) {
                Cookie c = cookieProvider.get(null, data, null);
                if(c==null) return 0.0;
                try {
                    return Double.parseDouble(c.getValue());
                } catch (NumberFormatException e) {
                    return 0.0;
                }
            }
        });
    }

    public void destroy() {
    }

    public boolean process(FilterChain chain) throws Exception {
        WebContext ctx = chain.getWebContext();
        ctx.getRequest().setCharacterEncoding(charset);
        WebContext oldctx = threadLocal.get();
        threadLocal.set(ctx);
        try {
            return chain.go();
        } finally {
            threadLocal.set(oldctx);
        }
    }

    public ThreadLocal<WebContext> getThreadLocal() {
        return threadLocal;
    }
}
